from binaryninja import (StringRecognizer, CustomStringType, DataBuffer, DerivedString, DerivedStringLocation,
                         DerivedStringLocationType, PointerType, InstructionTextToken, InstructionTextTokenType)

encoded_string_type = CustomStringType.register("Encoded", "", "_enc")


class EncodedStringRecognizer(StringRecognizer):
    recognizer_name = "encoded_strings"
    decoders = {
        "xor_encoded": lambda encoded, key: encoded ^ key,
        "sub_encoded": lambda encoded, key: (encoded - key) & 0xff,
        "add_encoded": lambda encoded, key: (encoded + key) & 0xff
    }

    def is_valid_for_type(self, func, type):
        if not isinstance(type, PointerType):
            return False
        for name in self.__class__.decoders.keys():
            if name in type.target.attributes:
                return True
        return False

    def recognize_constant_pointer(self, instr, type, val):
        if not isinstance(type, PointerType):
            return None

        values = None
        decoder = None
        for name in self.__class__.decoders.keys():
            if name in type.target.attributes:
                try:
                    values = bytes.fromhex(type.target.attributes[name])
                    decoder = self.__class__.decoders[name]
                except:
                    return None
        if values is None or decoder is None:
            return None

        encoded_null = "encoded_null" in type.target.attributes

        result = b""
        i = 0
        while True:
            byte = instr.function.view.read(val + i, 1)
            if len(byte) != 1:
                return False
            if not encoded_null and byte[0] == 0:
                break
            byte = decoder(byte[0], values[i % len(values)])
            if byte == 0:
                break
            result += bytes([byte])
            i += 1

        loc = DerivedStringLocation(DerivedStringLocationType.DataBackedStringLocation, val, i)
        return DerivedString(result, loc, encoded_string_type)


EncodedStringRecognizer().register()
